﻿using System;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace Unity.String.Tests
{
    public class UnityStringWriterTest
    {
        const int DefaultTaskWaitTimeout = 500;
        [Fact]
        public void ConstructorsTest()
        {
            UnityStringWriter testEmptyUnityStringWriter 
                = new UnityStringWriter();
            Assert.NotNull(testEmptyUnityStringWriter);
            
            Encoding testEncodingValue = testEmptyUnityStringWriter.Encoding;
            Encoding needEncoding = new UnicodeEncoding(false, false);
            Assert.Equal(needEncoding, testEncodingValue);

            UnityStringWriter testUnityStringWriterWithFormatProvider =
                new UnityStringWriter(CultureInfo.InvariantCulture);
            Assert.NotNull(testUnityStringWriterWithFormatProvider);
        }
        
        [Fact]
        public void MethodsTest()
        {
            UnityStringWriter testClosedUnityStringWriter 
                = new UnityStringWriter();
            testClosedUnityStringWriter.Close();
            string testStringValue = "NotDisposed";
            Assert.Throws<ObjectDisposedException>(() =>
            {
                testClosedUnityStringWriter.Write(testStringValue);
            });
        }
        
        [Fact]
        public void DoubleDisposeTest()
        {
            var unityStringWriter = new UnityStringWriter();
            unityStringWriter.Dispose();
            unityStringWriter.Dispose(); // call more than once
        }

        [Fact]
        public void WriteMethodsTest()
        {
            var writer = new UnityStringWriter();
            var needReturnString = "";
            
            string testTextValue = "text1";
            writer.Write(testTextValue);
            needReturnString += testTextValue;
            Assert.Equal(needReturnString, writer.ToString());
            
            
            ReadOnlySpan<char> testReadOnlySpanChar = "text2".AsSpan();
            writer.Write(testReadOnlySpanChar);
            needReturnString += testReadOnlySpanChar.ToString();
            Assert.Equal(needReturnString, writer.ToString());
            
            char testCharSymbol = 'c'; 
            writer.Write(testCharSymbol);
            needReturnString += testCharSymbol;
            Assert.Equal(needReturnString, writer.ToString());

            char[] testCharArray = new char[4]{'d', 'e', 'f', 'g'}; 
            writer.Write(testCharArray, 0, testCharArray.Length);
            needReturnString += new string(testCharArray);
            Assert.Equal(needReturnString, writer.ToString());
            
            Assert.Throws<ArgumentNullException>(() =>
            {
                writer.Write(null, 0, 0);
            });

            Assert.Throws<ArgumentOutOfRangeException>(() =>
            {
                writer.Write(testCharArray, -1, 0);
            });
            
            Assert.Throws<ArgumentOutOfRangeException>(() =>
            {
                writer.Write(testCharArray, 0, -1);
            });
            
            Assert.Throws<ArgumentException>(() =>
            {
                writer.Write(testCharArray, 0, testCharArray.Length + 1);
            });

            bool testBooleanValue = true;
            writer.Write(testBooleanValue);
            needReturnString += testBooleanValue;
            Assert.Equal(needReturnString, writer.ToString());

            int testIntValue = 123;
            writer.Write(testIntValue);
            needReturnString += testIntValue;
            Assert.Equal(needReturnString, writer.ToString());

            float testFloatValue = 456f;
            writer.Write(testFloatValue);
            needReturnString += testFloatValue;
            Assert.Equal(needReturnString, writer.ToString());

            double testDoubleValue = 789d;
            writer.Write(testDoubleValue);
            needReturnString += testDoubleValue;
            Assert.Equal(needReturnString, writer.ToString());

            decimal testDecimalValue = 987.654m;
            writer.Write(testDecimalValue);
            needReturnString += testDecimalValue;
            Assert.Equal(needReturnString, writer.ToString());
            
            ReadOnlyMemory<char> testReadOnlyMemoryChar = "end".AsMemory();
            writer.Write(testReadOnlyMemoryChar);
            needReturnString += testReadOnlyMemoryChar;
            Assert.Equal(needReturnString, writer.ToString());
            
            writer.WriteLine();
            needReturnString += Environment.NewLine;
            Assert.Equal(needReturnString, writer.ToString());
            
            writer.Close();
        }

        [Fact]
        public void WriteAsyncMethodsTest()
        {
            var writer = new UnityStringWriter();
            char testCharValue = 'a';
            Task returnWriteAsyncCharResult = writer.WriteAsync(testCharValue);
            returnWriteAsyncCharResult.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncCharResult.IsCompleted);
            
            string testStringValue = "bcdef";
            Task returnWriteAsyncStringResult = writer.WriteAsync(testStringValue);
            returnWriteAsyncStringResult.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncStringResult.IsCompleted);

            char[] testCharArray = "123456".ToCharArray();
            Task returnWriteAsyncCharArrayResult = writer.WriteAsync(testCharArray, 0, testCharArray.Length);
            returnWriteAsyncCharArrayResult.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncCharArrayResult.IsCompleted);

            ReadOnlyMemory<char> testCharReadOnlyMemory = new ReadOnlyMemory<char>("7890".ToCharArray());
            Task returnWriteAsyncReadOnlyMemoryCharArrayResult = writer.WriteAsync(testCharReadOnlyMemory);
            returnWriteAsyncReadOnlyMemoryCharArrayResult.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncReadOnlyMemoryCharArrayResult.IsCompleted);
            
            ReadOnlyMemory<char> testCharReadOnlyMemoryWithCancellationToken = 
                new ReadOnlyMemory<char>("7890".ToCharArray());
            Task returnWriteAsyncReadOnlyMemoryCharArrayResultWithCancellationToken = 
                writer.WriteAsync(testCharReadOnlyMemoryWithCancellationToken, CancellationToken.None);
            returnWriteAsyncReadOnlyMemoryCharArrayResultWithCancellationToken.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncReadOnlyMemoryCharArrayResultWithCancellationToken.IsCompleted);
            
            ReadOnlyMemory<char> testCharReadOnlyMemoryWithRequestedCancellationToken = 
                new ReadOnlyMemory<char>("7890".ToCharArray());
            CancellationToken testRequestedCancellationToken = new CancellationToken(true);
            Task returnWriteAsyncReadOnlyMemoryCharArrayResultWithRequestedCancellationToken = 
                writer.WriteAsync(testCharReadOnlyMemoryWithRequestedCancellationToken, testRequestedCancellationToken);
            Assert.Throws<AggregateException>(() =>
            {
                returnWriteAsyncReadOnlyMemoryCharArrayResultWithRequestedCancellationToken.Wait(DefaultTaskWaitTimeout);    
            });
            Assert.True(returnWriteAsyncReadOnlyMemoryCharArrayResultWithRequestedCancellationToken.IsCompleted);
            writer.Close();
        }
        
        [Fact]
        public void WriteLineAsyncMethodsTest()
        {
            var writer = new UnityStringWriter();
            char testCharValue = 'a';
            Task returnWriteAsyncCharResult = writer.WriteLineAsync(testCharValue);
            returnWriteAsyncCharResult.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncCharResult.IsCompleted);
            
            string testStringValue = "bcdef";
            Task returnWriteAsyncStringResult = writer.WriteLineAsync(testStringValue);
            returnWriteAsyncStringResult.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncStringResult.IsCompleted);

            char[] testCharArray = "123456".ToCharArray();
            Task returnWriteAsyncCharArrayResult = writer.WriteLineAsync(testCharArray, 0, testCharArray.Length);
            returnWriteAsyncCharArrayResult.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncCharArrayResult.IsCompleted);
            
            ReadOnlyMemory<char> testCharReadOnlyMemory = new ReadOnlyMemory<char>("7890".ToCharArray());
            Task returnWriteAsyncReadOnlyMemoryCharArrayResult = writer.WriteLineAsync(testCharReadOnlyMemory);
            returnWriteAsyncReadOnlyMemoryCharArrayResult.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncReadOnlyMemoryCharArrayResult.IsCompleted);
            
            ReadOnlyMemory<char> testCharReadOnlyMemoryWithCancellationToken = 
                new ReadOnlyMemory<char>("7890".ToCharArray());
            Task returnWriteAsyncReadOnlyMemoryCharArrayResultWithCancellationToken = 
                writer.WriteLineAsync(testCharReadOnlyMemoryWithCancellationToken, CancellationToken.None);
            returnWriteAsyncReadOnlyMemoryCharArrayResultWithCancellationToken.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncReadOnlyMemoryCharArrayResultWithCancellationToken.IsCompleted);

            ReadOnlyMemory<char> testCharReadOnlyMemoryWithRequestedCancellationToken = 
                new ReadOnlyMemory<char>("7890".ToCharArray());
            CancellationToken testRequestedCancellationToken = new CancellationToken(true);
            Task returnWriteAsyncReadOnlyMemoryCharArrayResultWithRequestedCancellationToken = 
                writer.WriteLineAsync(testCharReadOnlyMemoryWithRequestedCancellationToken, testRequestedCancellationToken);
            returnWriteAsyncReadOnlyMemoryCharArrayResultWithCancellationToken.Wait(DefaultTaskWaitTimeout);
            Assert.True(returnWriteAsyncReadOnlyMemoryCharArrayResultWithRequestedCancellationToken.IsCompleted);
            
            Task returnFlushAsyncResult = writer.FlushAsync();
            Assert.Throws<AggregateException>(() =>
            {
                returnWriteAsyncReadOnlyMemoryCharArrayResultWithRequestedCancellationToken.Wait(DefaultTaskWaitTimeout);
            });
            Assert.True(returnFlushAsyncResult.IsCompleted);

            writer.Close();
        }
    }
}
